/*!
\copyright  Copyright (c) 2024 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\version    
\file       dult_advertiser.c
\brief      Handles DULT advertising data
*/

#include <stdlib.h>
#include <string.h>
#include "bdaddr_.h"
#include "connection.h"
#include "le_advertising_manager_multi_set.h"
#include "logging.h"
#include "dult_advertiser.h"

#define DULT_ADV_SERVICE_TLV_SIZE                4
#define DULT_ADV_SERVICE_DATA_SIZE               6
#define DULT_ADV_OPT_FLAG_DATA_SIZE              3
#define DULT_ADV_ITEM_DATA_SIZE                  (DULT_ADV_SERVICE_DATA_SIZE + DULT_ADV_OPT_FLAG_DATA_SIZE)

#define DULT_ADV_FLAGS                           (BLE_FLAGS_GENERAL_DISCOVERABLE_MODE | BLE_FLAGS_DUAL_CONTROLLER | BLE_FLAGS_DUAL_HOST)

#define DULT_ADV_NETWORK_ID                      (uint8_t)0x0
#define DULT_NEAR_OWNER_MODE                     (uint8_t)0x01
#define DULT_SEPARATE_MODE                       (uint8_t)0x00

#define UUID_DULT_SERVICE                        0xFCB2

/*
 * Adv interval for DULT Adverts should be <= 4000ms, and ideally <= 2000ms (i.e. 0.5 Hz)
 * Adv Interval units are equal to 0.625ms.
 */
#define DULT_ADV_INTERVAL_MIN      (3000)
#define DULT_ADV_INTERVAL_MAX      (3200)

/* As per DULT Spec, RPA rotation is mandatory every 1024s(17minutes) */
#define DULT_ADV_RPA_ROTATION_TIME_MIN  (16)
#define DULT_ADV_RPA_ROTATION_TIME_MAX  (18)

/*! \brief Global data structure for DULT adverts */
typedef struct
{
    /*! The DULT advertising module task */
    TaskData task;
    le_adv_item_handle adv_item_handle;
    uint8_t *adv_data;
    size_t adv_data_size;
    tp_bdaddr  adv_bdaddr;
    bool near_owner;
    bool advertising_enabled;
} dult_advertiser_t;

/*! Global Instance of dult advertising data */
dult_advertiser_t dult_advertiser;

typedef struct {
#ifdef DULT_ADV_FLAGS
    uint8_t flags[DULT_ADV_OPT_FLAG_DATA_SIZE];
#endif
    uint8_t service_tlv[DULT_ADV_SERVICE_TLV_SIZE];
    uint8_t network_id;
    uint8_t near_owner;
} dult_common_adv_data_t;

static le_adv_item_data_t dult_adv_data_item;


static dult_common_adv_data_t dult_GetCommonAdvData(void)
{
    dult_common_adv_data_t adv_data;

#ifdef DULT_ADV_FLAGS
    adv_data.flags[0] = DULT_ADV_OPT_FLAG_DATA_SIZE - 1;
    adv_data.flags[1] = ble_ad_type_flags;
    adv_data.flags[2] = DULT_ADV_FLAGS;
#endif
    adv_data.service_tlv[0] = DULT_ADV_SERVICE_DATA_SIZE - 1;
    adv_data.service_tlv[1] = ble_ad_type_service_data;
    adv_data.service_tlv[2] = UUID_DULT_SERVICE & 0xFF;
    adv_data.service_tlv[3] = UUID_DULT_SERVICE >> 8;

    adv_data.network_id  = DULT_ADV_NETWORK_ID;
    adv_data.near_owner = dult_advertiser.near_owner;

    return adv_data;
}

bool dult_AdvNotifyDataChange(void)
{
    bool status = FALSE;
    if (dult_advertiser.adv_item_handle)
    {
        status = LeAdvertisingManager_UpdateAdvertisingItem(dult_advertiser.adv_item_handle);
    }
    else
    {
        DEBUG_LOG("DULT ADV: Invalid handle in dult_AdvNotifyDataChange");
    }
    return status;
}

static void dult_UpdateCommonAdvData(void)
{
    if(dult_advertiser.adv_data && dult_advertiser.adv_data_size >= sizeof(dult_common_adv_data_t))
    {
        dult_common_adv_data_t *common_adv_data = (dult_common_adv_data_t *) dult_advertiser.adv_data;
        
        *common_adv_data = dult_GetCommonAdvData();
    }
}

static inline le_adv_data_type_t dult_GetAdvType(void)
{
#if USE_EXTENDED_ADVERTS
    return le_adv_type_extended_connectable;
#else
    return le_adv_type_legacy_connectable_scannable;
#endif
}

static bool dult_GetItemData(le_adv_item_data_t * data)
{
    DEBUG_LOG("dult_GetItemData");
    PanicNull(data);
    *data = dult_adv_data_item;
    return TRUE;
}

static unsigned dult_GetItemDataSize(void)
{
    return dult_adv_data_item.size;
}

static bool dult_GetItemInfo(le_adv_item_info_t *info)
{
    PanicNull(info);
    *info = (le_adv_item_info_t){
        .placement = le_adv_item_data_placement_advert,
        .type = dult_GetAdvType(),
        .data_size = dult_GetItemDataSize(),
    };
    return TRUE;
}

static bool dult_GetItemParams(le_adv_item_params_t * params)
{
    DEBUG_LOG("dult_GetItemParams");

    PanicNull(params);

    if (dult_advertiser.adv_bdaddr.taddr.type == TYPED_BDADDR_INVALID)
    {
        params->random_addr_type = ble_local_addr_generate_resolvable;
        params->random_addr_generate_rotation_timeout_minimum_in_minutes = DULT_ADV_RPA_ROTATION_TIME_MIN;
        params->random_addr_generate_rotation_timeout_maximum_in_minutes = DULT_ADV_RPA_ROTATION_TIME_MAX;
    }
    else
    {
        params->random_addr_type = ble_local_addr_write_resolvable;

        params->random_addr.type = dult_advertiser.adv_bdaddr.taddr.type;
        params->random_addr.addr = dult_advertiser.adv_bdaddr.taddr.addr;

        params->random_addr_generate_rotation_timeout_minimum_in_minutes = 0;
        params->random_addr_generate_rotation_timeout_maximum_in_minutes = 0;

        DEBUG_LOG("dult_GetItemParams in UTP set addr %04x,%02x,%06lx",
                    params->random_addr.addr.nap, params->random_addr.addr.uap, params->random_addr.addr.lap);
    }
    params->own_addr_type = OWN_ADDRESS_RANDOM;

    params->primary_adv_interval_min = DULT_ADV_INTERVAL_MIN;
    params->primary_adv_interval_max = DULT_ADV_INTERVAL_MAX;

    return TRUE;
}

static void dult_ReleaseItemData(void)
{
    if(dult_advertiser.adv_data)
    {
        free(dult_advertiser.adv_data);
        dult_advertiser.adv_data=NULL;
        dult_advertiser.adv_data_size = 0;
        dult_adv_data_item.size = 0;
    }
}

static const le_adv_item_callback_t dult_advertising_callback = {
    .GetItemData = &dult_GetItemData,
    .GetItemParameters = &dult_GetItemParams,
    .GetItemInfo = &dult_GetItemInfo,
    .ReleaseItemData = &dult_ReleaseItemData,
};

static void dult_updateAdvertisingState(void)
{
    DEBUG_LOG("dult_updateAdvertisingState");
    if(dult_advertiser.advertising_enabled)
    {
        if(dult_advertiser.adv_item_handle == NULL)
        {
            dult_advertiser.adv_item_handle =
                LeAdvertisingManager_RegisterAdvertisingItemCallback(NULL, &dult_advertising_callback);
        }
    }
    else if(dult_advertiser.adv_item_handle)
    {
        LeAdvertisingManager_UnregisterAdvertisingItem(dult_advertiser.adv_item_handle);
        dult_advertiser.advertising_enabled = false;
    }
}

bool dult_EnableAdvertising(void)
{
    DEBUG_LOG("dult_EnableAdvertising");
    dult_advertiser.advertising_enabled = true;
    dult_updateAdvertisingState();
    dult_AdvNotifyDataChange();

    return dult_advertiser.advertising_enabled;
}

void dult_DisableAdvertising(void)
{
    DEBUG_LOG("dult_DisableAdvertising");
    dult_advertiser.advertising_enabled = false;
    dult_updateAdvertisingState();
}

void dult_SetAdvAddress(bdaddr addr)
{
    DEBUG_LOG("dult_SetAdvAddress");
    dult_advertiser.adv_bdaddr =
        (tp_bdaddr){.taddr = (typed_bdaddr){.addr = addr, .type = TYPED_BDADDR_RANDOM}, .transport = TRANSPORT_BLE_ACL};
    
    dult_AdvNotifyDataChange();   
}

le_adv_item_data_t dult_GetDultAdvData(void)
{
    return dult_adv_data_item;
}

void dult_SetProprietaryPayload(const uint8_t *const payload, const size_t payload_size)
{
    DEBUG_LOG("dult_SetProprietaryPayload");
    if(dult_advertiser.adv_data)
    {
        free(dult_advertiser.adv_data);

        dult_advertiser.adv_data_size = 0;
        dult_adv_data_item.size = 0;
    }

    size_t adv_size = DULT_ADV_ITEM_DATA_SIZE + payload_size;
    uint8_t *adv_data;

    adv_data = PanicUnlessMalloc(adv_size);

    dult_common_adv_data_t common_adv_data = dult_GetCommonAdvData();

    memcpy(adv_data, &common_adv_data, sizeof(dult_common_adv_data_t));

    if(payload)
    {
        memcpy(&adv_data[sizeof(dult_common_adv_data_t)], payload, payload_size);
    }

    // Update advertiser state
    dult_advertiser.adv_data = adv_data;
    dult_advertiser.adv_data_size = adv_size;

    // Update le_adv_item_data_t state
    dult_adv_data_item.data = adv_data;
    dult_adv_data_item.size = adv_size;

    dult_AdvNotifyDataChange();

    DEBUG_LOG("dult_SetProprietaryPayload size=%d", adv_size);
    DEBUG_LOG_DATA(adv_data,(uint16_t) adv_size);
}

static void dult_InitAdvData(void)
{
    dult_SetProprietaryPayload(NULL, 0);
}

void dult_InitAdvertising(void)
{
    DEBUG_LOG("dult_InitAdvertising");
    dult_advertiser = (dult_advertiser_t) {
        .advertising_enabled = true,
        .near_owner = true,
        .adv_bdaddr = (tp_bdaddr){.taddr = (typed_bdaddr){.type = TYPED_BDADDR_INVALID}, .transport = TRANSPORT_NONE},
    };

    dult_InitAdvData();

}

void dult_SetNearOwner(bool near_owner)
{
    DEBUG_LOG("dult_SetNearOwner");
    dult_advertiser.near_owner = near_owner;
    dult_UpdateCommonAdvData();
    dult_AdvNotifyDataChange();
}
